package club.jdiy.utils;

import club.jdiy.core.base.domain.DBEntity;
import club.jdiy.core.convert.Converter;
import club.jdiy.core.sql.Rs;

import javax.validation.constraints.NotNull;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * JDiy Bean 工具类.
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20220622
 */
public final class BeanUtils {

    /**
     * 将fromBean中的属性字段值复制到toBean. <br/>
     * 它比使用{@link java.beans.PropertyDescriptor}复制更加智能.<br/>
     * 例如同名属性若数据类型不一致，它会尝试使用JDiy的{@link Converter#to(Class)}类型互转器完成转换．<br/>
     * 如果toBean是一个DBEntity，则复制时自动跳过实体的主键（id）字段.
     *
     * @param fromBean 要复制的原bean
     * @param toBean   要复制数据到的目标bean
     * @param <T>      目标Bean的class类型
     * @return 返回复制数据后的目标bean本身.
     * @see #copyNotEmpty(Object, Object)
     */
    @SuppressWarnings("ALL")
    public static <T> T copy(Object fromBean, T toBean) {
        if (fromBean != null && toBean != null) {
            if (fromBean instanceof Map) _cpMapToBean((Map) fromBean, toBean, false);
            else _cpBeanToBean(fromBean, toBean, false);
        }
        return toBean;
    }

    /**
     * 将fromBean中的非空(null)属性字段值复制到toBean. <br/>
     * 它比使用{@link java.beans.PropertyDescriptor}复制更加智能. <br/>
     * 例如同名属性若数据类型不一致，它会尝试使用JDiy的{@link Converter#to(Class)}类型互转器完成转换．<br/>
     * 如果toBean是一个DBEntity，则复制时自动跳过id主键.
     *
     * @param fromBean 要复制的原bean
     * @param toBean   要复制数据到的目标bean
     * @param <T>      目标Bean的class类型
     * @return 返回复制数据后的目标bean本身.
     * @see #copy(Object, Object)
     */
    @SuppressWarnings("ALL")
    public static <T> T copyNotNull(Object fromBean, T toBean) {
        if (fromBean != null && toBean != null) {
            if (fromBean instanceof Map) _cpMapToBean((Map) fromBean, toBean, true);
            else _cpBeanToBean(fromBean, toBean, true);
        }
        return toBean;
    }

    public static <T> T copy(Map<String, ?> fromMap, T toBean) {
        copyNotNull(fromMap, toBean);
        return toBean;
    }

    @SuppressWarnings("ALL")
    private static void _cpBeanToBean(Object fromBean, Object toBean, boolean skipNull) {
        final Map<String, Method> getters = ReflexUtils.getGetters(fromBean.getClass());
        if (toBean instanceof Rs) {
            final Rs toRs = (Rs) toBean;
            getters.forEach((k, v) -> {
                if (!k.equals(toRs.getPrimaryKey())//跳过主键
                        && !Collection.class.isAssignableFrom(v.getClass())//跳过集合(如List)不能copy,否则从Entity复制可能报StackOverflow错误．
                ) {
                    try {
                        Object val = v.invoke(fromBean);
                        if (val != null || !skipNull) {
                            //如果fromBean中的目标字段是一个实体引用，则设置到rs中以xxx_id命名．
                            if (DBEntity.class.isAssignableFrom(v.getReturnType())) {
                                val = ((DBEntity) val).getId();
                                toRs.set(k + "_id", Converter.of(val).to(val.getClass()));
                            } else
                                toRs.set(k, Converter.of(val).to(val.getClass()));//使用converter确保目标类型是rs支持的基本数据类型
                        }
                    } catch (Exception ignore) {
                        //ignore.printStackTrace();
                    }
                }
            });
        } else if (toBean instanceof Map) {
            try {
                Class<?> valueClass = toBean.getClass().getMethod("get").getReturnType();
                final Rs toMap = (Rs) toBean;
                getters.forEach((k, v) -> {
                    try {
                        Object val = v.invoke(fromBean);
                        if (val == null && !skipNull) toMap.remove(k);
                        else {
                            if (val.getClass().equals(valueClass)) toMap.put(k, val);
                            else {
                                toMap.put(k, Converter.of(val).to(valueClass));
                            }
                        }
                    } catch (Exception ignore) {
                        //ignore.printStackTrace();
                    }
                });
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        } else {
            final Map<String, Method> setters = ReflexUtils.getSetters(toBean.getClass());
            getters.forEach((k, v) -> {
                Method sm = "id".equals(k) && toBean instanceof DBEntity ? null : setters.get(k);
                //集合类(如List)不能copy,否则从Entity复制可能报StackOverflow错误．
                if (!Collection.class.isAssignableFrom(v.getReturnType()) && sm != null) try {
                    Object val = v.invoke(fromBean);
                    if (val != null || !skipNull) {
                        if (val == null) sm.invoke(toBean, null);
                        else if (DBEntity.class.isAssignableFrom(v.getReturnType())) {//如果目标字段是一个实体引用，则创建一个新的实体对象并设置id,再赋予toBean:
                            DBEntity got = ((DBEntity) v.getReturnType().getDeclaredConstructor().newInstance());
                            got.setId(((DBEntity) val).getId());
                            sm.invoke(toBean, got);
                        } else {
                            sm.invoke(toBean, Converter.of(val).to(sm.getParameterTypes()[0]));
                        }
                    }
                } catch (Exception ignore) {
                    //ignore.printStackTrace();
                }
            });
        }
    }


    @SuppressWarnings("ALL")
    private static void _cpMapToBean(@NotNull Map map, @NotNull Object toBean, boolean skipNull) {
        if (toBean instanceof Rs) {
            Rs rs = (Rs) toBean;
            map.forEach((k, v) -> {
                if (!k.equals(rs.getPrimaryKey()) && (v != null || !skipNull)) rs.put(k.toString(), v);//跳过toBean的主键
            });
        } else if (toBean instanceof Map) {
            if (skipNull) {
                Rs rs = (Rs) toBean;
                map.forEach((k, v) -> {
                    if (v != null) rs.put(k.toString(), v);
                });
            } else
                ((Map) toBean).putAll(map);
        } else {
            final Map<String, Method> setters = ReflexUtils.getSetters(toBean.getClass());
            map.forEach((k, v) -> {
                //集合类(如List)不能copy,否则从Entity复制可能报StackOverflow错误．
                if (v != null && !Collection.class.isAssignableFrom(v.getClass())
                        || v == null && !skipNull //是否跳过对空值的处理
                )
                    //当目标bean是一个JDiy实体时，避免把目标实体的主键给覆盖了:
                    Optional.ofNullable(toBean instanceof DBEntity && "id".equals(k) ? null : setters.get(k)).ifPresent(sm -> {
                        try {
                            sm.invoke(toBean, Converter.of(v).to(sm.getParameterTypes()[0]));
                        } catch (Exception ignore) {
                        }
                    });
            });
        }
    }

    /**
     * 反射方式调用某个bean的setXxx方法(其中xxx为属性名)，来设置某数据到bean. <br/>
     * 它比使用{@link java.beans.PropertyDescriptor}更加智能,例如：<br/>
     * 要设置的数据类型与setter方法入参类型不一致时，它会尝试自动进行类型转换;<br/>
     * 同时它还支持多层子属性设置.
     *
     * @param bean  Bean对象，可以是任意的Object,例如各类VO, PO, DTO,Entity等带相关getter或setter的数据载体,或者Map对象。<br/><br/>
     * @param field 要设置的属性字段名称. <br/>
     *              例如 field=“name”时，将调用 bean.setName(value)方法;<br/>
     *              若bean是一个Map, 则调用 bean.set("name",value);<br/>
     *              它还支持多层子属性设置。如： <br/>
     *              BeanUtils.set(user,"role.id",5);//设置user的role子对象的id的值为5<br/><br/>
     * @param value 要设置的值. 当与目标字段类型不一致时，会使用JDiy的{@link Converter#to(Class)}转换器自动完成类型转换。<br/>
     * @throws ReflectiveOperationException ex
     * @throws IntrospectionException       ex
     * @see Converter
     */
    public static void set(Object bean, String field, Object value) throws ReflectiveOperationException, IntrospectionException {
        String member;
        int dp = field.indexOf(".");
        if (dp > -1) {
            member = field.substring(dp + 1);
            field = field.substring(0, dp);
            PropertyDescriptor descriptor = new PropertyDescriptor(field, bean.getClass());
            Object bean1 = descriptor.getReadMethod().invoke(bean);
            if (bean1 == null) descriptor.getWriteMethod().invoke(bean,
                    bean1 = Map.class.isAssignableFrom(descriptor.getPropertyType())
                            ? new HashMap<>()
                            : descriptor.getPropertyType().getDeclaredConstructor().newInstance());
            set(bean1, member, value);
        } else {
            if (bean instanceof Map) {
                //noinspection ALL
                ((Map) bean).put(field, value);
                return;
            }
            //尝试调用value值类型相匹配的setter.
            if (value != null) try {
                bean.getClass().getMethod("set" + StringUtils.capitalize(field), value.getClass()).invoke(bean, value);
                return;
            } catch (Exception ignore) {
            }

            //若上面setter调用失败(匹配不到)，则尝试获取同名setter, 并将value转换为满足该setter入参的类型后再调用
            Method setter = ReflexUtils.getSetters(bean.getClass()).get(field);
            if (setter != null) try {
                setter.invoke(bean, value == null ? null : Converter.of(value).to(setter.getParameterTypes()[0]));
                return;
            } catch (Exception ignore) {
                throw new IllegalArgumentException(
                        "call setter '" + setter.toString() + "' failed with argument '" + value + "'" +
                                (value != null ? " (" + value.getClass().getName() + ")" : ""));
            }
            throw new NoSuchMethodException("No setter: '" + bean.getClass().getName() +
                    ".set" + StringUtils.capitalize(field) + "(" +
                    (value == null ? "null" : value.getClass().getName())
                    + ")'");
        }
    }


    /**
     * 查找匹配目标class的指定的方法（忽略形参的基本类型和对象类型差异;优先返回严格一致的形参方法）.
     * 例如： <br/>
     * getMethodIgnoreBasicTypes(Xxx.class, "some",new Class[]{int.class});<br/>
     * 优先匹配查找并返回 Xxx.some(int x) 方法， 若它不存在，再匹配  Xxx.some(Integer.class) 方法并返回.
     *
     * @param c                目标class
     * @param methodName       目标方法名称
     * @param methodParamTypes 形参列表（忽略基本类型和对象类型差异）
     * @return 返回匹配到的方法.
     */
    public static Optional<Method> getMethodIgnoreBasicTypes(Class<?> c, String methodName, Class<?>[] methodParamTypes) {
        try {
            if (methodParamTypes == null || methodParamTypes.length < 1) return Optional.of(c.getMethod(methodName));
        } catch (Exception ex) {
            return Optional.empty();
        }
        try {
            return Optional.of(c.getMethod(methodName, methodParamTypes));
        } catch (Exception ignore) {
            Method[] methods = c.getDeclaredMethods();
            for (Method m : methods) {
                Class<?>[] mTypes = m.getParameterTypes();
                if (methodParamTypes.length == mTypes.length) {
                    for (int i = 0; i < mTypes.length; i++) {
                        if (!matchTypes(mTypes[i], methodParamTypes[i])) return Optional.empty();
                    }
                    return Optional.of(m);
                }
            }
            return Optional.empty();
        }
    }

    private static boolean matchTypes(Class<?> c1, Class<?> c2) {
        return c1 == c2 ||
                (c1 == boolean.class && c2 == Boolean.class || c1 == Boolean.class && c2 == boolean.class) ||
                (c1 == int.class && c2 == Integer.class || c1 == Integer.class && c2 == int.class) ||
                (c1 == long.class && c2 == Long.class || c1 == Long.class && c2 == long.class) ||
                (c1 == double.class && c2 == Double.class || c1 == Double.class && c2 == double.class) ||
                (c1 == float.class && c2 == Float.class || c1 == Float.class && c2 == float.class);
    }


    /**
     * 对象深度克隆.
     *
     * @param src 源对象
     * @param <T> 对象泛型类型
     * @return 克隆后的新对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T clone(T src) {
        try (ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
             ObjectOutputStream out = new ObjectOutputStream(memoryBuffer)) {
            out.writeObject(src);
            out.flush();
            try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()))) {
                return (T) in.readObject();
            }
        } catch (Exception ie) {
            ie.printStackTrace();
            throw new RuntimeException(ie.getMessage());
        }
    }

    private BeanUtils() {
    }
}

